/*
 *		This Code Was Created By Jeff Molofee 2000
 *		A HUGE Thanks To Fredric Echols For Cleaning Up
 *		And Optimizing This Code, Making It More Flexible!
 *		If You've Found This Code Useful, Please Let Me Know.
 *		Visit My Site At nehe.gamedev.net
 */
#include "main.h"
#include "BaseApp.h"
#include "InputState.h"
#include "InputStateManager.h"

BaseApp baseApp;
InputStateManager inputStateManager;
HDC			_hDC=NULL;		// Private GDI Device Context
HGLRC		_hRC=NULL;		// Permanent Rendering Context
HWND		_hWnd=NULL;		// Holds Our Window Handle
HINSTANCE	_hInstance;		// Holds The Instance Of The Application

bool _bRunning = true;

LRESULT	CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);	// Declaration For WndProc

void ReSizeGLScene(int width, int height)		// Resize And Initialize The GL Window
{
	if (height==0)										// Prevent A Divide By Zero By
	{
		height=1;										// Making Height Equal One
	}

	glViewport(0,0,width,height);						// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix
}

bool InitGL()
{
    GLenum err = glewInit();
    bool m_GlewOK = (err == GLEW_OK);

    glShadeModel(GL_SMOOTH);
    glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
    glClearDepth(1.0f);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    // extension function - enable vertical syncing.
    wglSwapIntervalEXT(1);

	ilInit();
	iluInit();
	ilutRenderer(ILUT_OPENGL);

	return m_GlewOK;
}

void KillGLWindow()
{
	if (_hRC)
	{
		if (!wglMakeCurrent(NULL, NULL))
		{
			MessageBox(NULL, L"Release of DC and RC failed.", L"SHUTDOWN ERROR", MB_OK | MB_ICONEXCLAMATION);
		}
		if (!wglDeleteContext(_hRC))
		{
			MessageBox(NULL,L"Release Rendering Context Failed.",L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		}
		_hRC = NULL;
	}

	if (_hDC && !ReleaseDC(_hWnd, _hDC))
	{
		MessageBox(NULL,L"Release Device Context Failed.",L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		_hDC=NULL;										// Set DC To NULL
	}

	if (_hWnd && !DestroyWindow(_hWnd))					// Are We Able To Destroy The Window?
	{
		MessageBox(NULL,L"Could Not Release _hWnd.",L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		_hWnd=NULL;										// Set _hWnd To NULL
	}

	if (!UnregisterClass(L"OpenGL",_hInstance))			// Are We Able To Unregister Class
	{
		MessageBox(NULL,L"Could Not Unregister Class.",L"SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
		_hInstance=NULL;									// Set _hInstance To NULL
	}
}

bool CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
	GLuint PixelFormat;
	WNDCLASS windowClass;
	DWORD dwExStyle;
	DWORD dwStyle;

	RECT WindowRect;
	WindowRect.left				= (long)0;
	WindowRect.right			= (long)width;
	WindowRect.top				= (long)0;
	WindowRect.bottom			= (long)height;

	_hInstance					= GetModuleHandle(NULL);
	windowClass.style			= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	windowClass.lpfnWndProc		= (WNDPROC) WndProc;
	windowClass.cbClsExtra		= 0;
	windowClass.cbWndExtra		= 0;
	windowClass.hInstance		= _hInstance;
	windowClass.hIcon			= LoadIcon(NULL, IDI_WINLOGO);
	windowClass.hCursor			= LoadCursor(NULL, IDC_ARROW);
	windowClass.hbrBackground	= NULL;
	windowClass.lpszMenuName	= NULL;
	windowClass.lpszClassName	= L"OpenGL";

	if (!RegisterClass(&windowClass))
	{
		MessageBox(NULL, L"Failed to register the window class.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
	dwStyle = WS_OVERLAPPEDWINDOW;

	AdjustWindowRectEx(&WindowRect, dwStyle, false, dwExStyle);

	// Create the window
	if (!(_hWnd = CreateWindowEx(	dwExStyle,
									L"OpenGL",
									L"Title",
									dwStyle | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
									0,
									0,
									WindowRect.right - WindowRect.left,
									WindowRect.bottom - WindowRect.top,
									NULL,
									NULL,
									_hInstance,
									NULL)))
	{
		KillGLWindow();
		MessageBox(NULL, L"Window creation error.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	static PIXELFORMATDESCRIPTOR pfd = 
	{
		sizeof(PIXELFORMATDESCRIPTOR),				// Size Of This Pixel Format Descriptor
		1,											// Version Number
		PFD_DRAW_TO_WINDOW |						// Format Must Support Window
		PFD_SUPPORT_OPENGL |						// Format Must Support OpenGL
		PFD_DOUBLEBUFFER,							// Must Support Double Buffering
		PFD_TYPE_RGBA,								// Request An RGBA Format
		bits,										// Select Our Color Depth
		0, 0, 0, 0, 0, 0,							// Color Bits Ignored
		0,											// No Alpha Buffer
		0,											// Shift Bit Ignored
		0,											// No Accumulation Buffer
		0, 0, 0, 0,									// Accumulation Bits Ignored
		16,											// 16Bit Z-Buffer (Depth Buffer)  
		0,											// No Stencil Buffer
		0,											// No Auxiliary Buffer
		PFD_MAIN_PLANE,								// Main Drawing Layer
		0,											// Reserved
		0, 0, 0										// Layer Masks Ignored
	};

	if (!(_hDC = GetDC(_hWnd)))
	{
		KillGLWindow();
		MessageBox(NULL, L"Can't create a GL device context.", L"ERROR", MB_OK | MB_ICONEXCLAMATION);
		return false;
	}

	if (!(PixelFormat=ChoosePixelFormat(_hDC,&pfd)))	// Did Windows Find A Matching Pixel Format?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,L"Can't Find A Suitable PixelFormat.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;								// Return FALSE
	}

	if(!SetPixelFormat(_hDC,PixelFormat,&pfd))		// Are We Able To Set The Pixel Format?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,L"Can't Set The PixelFormat.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;								// Return FALSE
	}

	if (!(_hRC=wglCreateContext(_hDC)))				// Are We Able To Get A Rendering Context?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,L"Can't Create A GL Rendering Context.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;								// Return FALSE
	}

	if(!wglMakeCurrent(_hDC,_hRC))					// Try To Activate The Rendering Context
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,L"Can't Activate The GL Rendering Context.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;								// Return FALSE
	}

	ShowWindow(_hWnd,SW_SHOW);						// Show The Window
	SetForegroundWindow(_hWnd);						// Slightly Higher Priority
	SetFocus(_hWnd);									// Sets Keyboard Focus To The Window
	ReSizeGLScene(width, height);					// Set Up Our Perspective GL Screen

	if (!InitGL())									// Initialize Our Newly Created GL Window
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,L"Initialization Failed.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;								// Return FALSE
	}

	return true;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
	switch (message) {
		case WM_SIZE: // If our window is resizing
		{
			ReSizeGLScene(LOWORD(lParam), HIWORD(lParam)); // Send the new window size to our OpenGLContext
			return 0;
		}

		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}

		case WM_MOUSEMOVE:
		{
			inputStateManager.setMousePos(MAKEPOINTS(lParam));
			return 0;
		}

		case WM_LBUTTONDOWN:
		{
			baseApp.keyPressed(wParam);
			inputStateManager.setLMBPressed(true);
			return 0;
		}
		
		case WM_LBUTTONUP:
		{
			baseApp.keyPressed(wParam);
			inputStateManager.setLMBPressed(false);
			return 0;
		}

		case WM_RBUTTONDOWN:
		{
			baseApp.keyPressed(wParam);
			inputStateManager.setRMBPressed(true);
			return 0;
		}

		case WM_RBUTTONUP:
		{
			baseApp.keyPressed(wParam);
			inputStateManager.setRMBPressed(false);
			return 0;
		}

		case WM_MOUSEWHEEL:
		{
			inputStateManager.setWheelPos(GET_WHEEL_DELTA_WPARAM(wParam));
			return 0;
		}

		case WM_KEYUP:
		{
			baseApp.keyPressed(wParam);
			return 0;
		}
	}

	return DefWindowProc(hWnd, message, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
{
	MSG msg;
	bool _bRunning = true;

	if (!CreateGLWindow("Window", 1280,960,16,false))
	{
		return 0;
	}

	while (_bRunning)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // If we have a message to process, process it
			if (msg.message == WM_QUIT) {
				_bRunning = false; // Set _bRunning to false if we have a message to quit
			}
			else {
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}
		else { // If we don't have a message to process
			InputState refInputState = inputStateManager.GenerateInputState();
			baseApp.updateScene(refInputState);
			baseApp.drawScene();
			SwapBuffers(_hDC);
		}
	}

	KillGLWindow();

	return (int) msg.wParam;
}